#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <dirent.h>
#include <fcntl.h>
#include <host.h>
#include <ants.h>
#include <wait_read.h>
#include <http.h>
#include <ftp.h>
#include <config.h>
	
int buffer_size(int size, int *thread) {
	int i, q, n;
	if(!thread)
		return 0;
	n = *thread;
	if(n < 1 )
		n = 1;
	else if(n > MAX_THREAD)
		n = MAX_THREAD;
	i = size / n;
	i >>= 4;
	if(!i) {	/*fixup very tiny file size( < thread*16) */
		n = 1;
		i = size;
	}
	while(i < MIN_BUF_SIZE && n > 1) {
		i <<= 1;
		n >>= 1;
	}
	if(i < MIN_BUF_SIZE)
		i = MIN_BUF_SIZE;
	else if(i > MAX_BUF_SIZE)
		i = MAX_BUF_SIZE;
	if(n != *thread)
		*thread = n;
	if(i > PACKSIZE) {
		if((q = i % PACKSIZE))
			i -= q;
	}
	return i;
}	
	
int statistic(struct ants *owner) {
	if(owner == NULL)
		return 0;
	int i, left, this_read, total, thread;
	long long cost;
	double percent, speed;
	static struct timeval tv, last;
	static int show;
	struct mission *pms = owner->pms;
	struct ants *ant = pms->first_ant;
	if(++show >= 20)
		show = 0;
	else
		return -1;
	
	total = pms->size;
	thread = pms->thread;
	gettimeofday(&tv, NULL);
	speed = -1;
	
	left = 0;
	if(pms->oneshot) {
		pms->oneshot = 0;
		last = tv;
		for(i = 0; i < thread; i++)
			left += (ant[i].end - ant[i].position + 1);
		pms->count = total - left;
	}
	else {
		for(i = 0; i < thread; i++)
			left += (ant[i].end - ant[i].position + 1);
		this_read = (total - left) - pms->count;
		pms->count = total - left;
		
		cost = (tv.tv_sec - last.tv_sec) * 1000000 + (tv.tv_usec + last.tv_usec);
		last = tv;
		cost /= 1000;
		percent = pms->count / (total / 100);
		this_read /= 1024;
		speed = (this_read * 1000) /cost;
		printf("- - - - - - -DOWNLOAD: %4.1f %%- - -DOWNLOAD SPEED :  %4.1f Kb/s .\n", percent, speed);
	}
	return (int)speed;
}	
	
void fill_ants(struct ants *ant, struct mission *pms, int size) {
	int i, bytes, thread, start;
	if((ant == NULL) || (pms == NULL))
		return;
	start = 0;
	thread = pms->thread;
	bytes = pms->size / thread;
	for(i = 0; i < thread; i++) {
		ant[i].flag = 0;
		ant[i].thread = i + 1;
		ant[i].pms = pms;
		ant[i].buf_size = size;
		ant[i].start = start;
		ant[i].position = start;
		ant[i].end = ant[i].start + bytes -1;
		start = ant[i].end + 1;
		ant[i].s = 0;
		ant[i].s_c = 0;
		ant[i].fd = 0;
	}
	ant[thread - 1].end = pms->size - 1;
}
	
void state_init(struct mission *pms) {
	int i, fd, thread;
	int num[MAX_THREAD];
	if(pms == NULL)
		return;
	struct ants *ant = pms->first_ant;
	if(ant == NULL)
		return;
	fd = pms->fd;
	thread = pms->thread;
	for(i = 0; i < thread; i++)
		num[i] = ant[i].end;
	lseek(fd, 0, 0);
	write(fd, pms, sizeof(struct mission));
	write(fd, num, sizeof(int) * thread);
	state_save(pms); /*save position first*/
}
	
void state_save(struct mission *pms) {
	int i, fd, thread, offset;
	int num[MAX_THREAD];
	if(pms == NULL)
		return;
	struct ants *ant = pms->first_ant;
	if(ant == NULL)
		return;
	fd = pms->fd;
	thread = pms->thread;
	offset = sizeof(struct mission) + (pms->thread * sizeof(int));
	for(i = 0; i < thread; i++)
		num[i] = ant[i].position;
	lseek(fd, offset, 0);
	write(fd, num, sizeof(int) * thread);
}
	
	
int scan_temp(char *path) {
	int i, num, fd;
	char *p;
	char filename[MAX_HOSTNAME];
	struct dirent **namelist;
	
	if(path == NULL)
		return -1;
	chdir(path);
	if(( num = scandir(".", &namelist, 0 , alphasort)) < 0)
		return -1;
	for(i = 0; i < num; i++) {
		strcpy(filename, namelist[i]->d_name);
		p = rindex(filename, '.');
		if(p++) {
			if(strcmp(p, "nat") == 0) {
				fd = open(filename, O_RDWR, 0644);
				if(fd)
					return fd;
			}
		}
	}
	return -1;
}	
	
int build_ants(struct mission *pms, char *name, int *thread) {
	int size, fd;
	struct ants *ant;
	char file[MAX_HOSTNAME];
	if((pms == NULL) || (name == NULL) || (thread == NULL))
		return 0;
	size = 0;
	pms->h = 0;
	if(!fill_mission(pms, name))
		return 0;
	if(pms->port == 80)
		size = get_http_size(pms);
	else if(pms->port == 21)
		size = get_ftp_size(pms);
	if(size == -1 || size == 0)
		return 0;
	if(!(pms->flag & BREAKPOINT))
		*thread = 1;
	pms ->size = size;
	size = buffer_size(size, thread);   /* and fixup thread number*/
	if(!size)
		return 0;
	printf("buffer size is %d.\n", size);
	pms->thread = *thread;
	pms->oneshot = 1;
	pms->count = 0;
	ant = malloc(sizeof(struct ants) * (*thread));
	pms->first_ant = ant;
	fill_ants(ant, pms, size);
	pms->fd = 0;
	if(pms->flag & BREAKPOINT) {
		sprintf(file, "%s%s.nat",pms->file_path, pms->file);
		fd = open(file, O_WRONLY|O_CREAT, 0644);
		if(fd <= 0) {
			printf("can't open temp file\n");
			return 0;
		}
		pms->fd = fd;
		state_init(pms);
	}
	return 1;
}	
	
int rebuild_ants(int fd, struct mission *pms) {
	int i, n, one, thread, old_thread;
	char *save_path;
	struct ants *ant;
	int num[MAX_THREAD * 2];
	if(pms == NULL)
		return 0;
	n = 0;
	one = 1;
	lseek(fd, 0, 0);
	save_path = pms->file_path;
	read(fd, pms, sizeof(struct mission));
	pms->file_path = save_path;
	pms->h = 0;
	pms->fd = fd;
	thread = pms->thread;
	read(fd, num, (sizeof(int) * thread * 2));
	for(i = 0; i < thread; i++) {
		while((i < thread) && (num[i] < num[i + thread]))	/*exclude download 100% thread*/
			i++;
		if(i >= thread)
			break;
		if(i != n) {
			num[n] = num[i];
			num[n + thread] = num[i + thread];
		}
		n++;
	}
	if(!n) {
		char file[MAX_HOSTNAME];
		int thread = pms->thread;
		char *path = pms->file_path;
		close(pms->fd);
		sprintf(file, "%s%s.nat", pms->file_path, pms->file);
		del_file(file);
		memset(pms, 0, sizeof(struct mission));
		pms->thread = thread;
		pms->file_path = path;
		return 0;
	}
	old_thread = thread;
	thread = n;
	
	ant = malloc(sizeof(struct ants) * thread);
	pms->first_ant = ant;
	pms->oneshot = 1;
	pms->thread = thread;
	for(i = 0; i < thread; i++) {
		ant[i].s = 0;
		ant[i].s_c = 0;
		ant[i].fd = 0;
		ant[i].flag = 0;
		ant[i].thread = i + 1;
		ant[i].pms = pms;
		ant[i].start = num[i + old_thread];
		ant[i].position = num[i + old_thread];
		ant[i].end = num[i];
		ant[i].buf_size = buffer_size((ant[i].end - ant[i].position + 1), &one);
	}
	state_init(pms);
	return 1;
}	
	
static int fill_buffer(struct ants *ant, int *change) {
		int read;
		if(ant == NULL)
			return -1;
		if(ant->offset + ant->packsize > ant->buf_size)
			ant->packsize = ant->buf_size - ant->offset;
		if(ant->packsize > ant->bytesleft)
			ant->packsize = ant->bytesleft;
	
		if((ant->pms->port == 80) && (ant->first)) {
			int start_text;
			ant->first = 0;
			read = recv(ant->s, ant->data, ant->packsize, MSG_WAITALL);
			/*extract head of http connect*/
			start_text = find_start(ant->data, read);
			if(start_text == -1)
				return -1;
			read -= start_text;
			memcpy(ant->buffer, ant->data + start_text, read);
		}
		else {
			read = recv(ant->s, ant->data, ant->packsize, MSG_WAITALL);
			memcpy(ant->buffer, ant->data, read);
		}
	
		ant->buffer += read;
		ant->bytesleft -= read;
		ant->offset += read;
	
		if(ant->bytesleft && ((ant->offset + ant->packsize) <= ant->buf_size) && !(ant->pms->flag & NOBUFFER))
			return 0;
		/*write to harddisk*/
		write(ant->fd, ant->buffer_start, ant->offset);
		ant->position += ant->offset;
		*change = 1; /*need save state to temp file*/
	
		ant->offset = 0;
		ant->packsize = PACKSIZE;
		ant->buffer = ant->buffer_start;
		lseek(ant->fd, ant->position, 0);
		if(ant->bytesleft)
			return 1;
		ant->flag |= FINISH;
		return 2;
}	
	
static int down_fragment(struct mission *pms) {
	char *data;
	char *buffer_start;
	int ret, finish_num;
	struct ants *ant;
	int i, j, thread, size;
	int speed, state, change;
	fd_set rfdset;
	if(pms == NULL)
		return -1;
	finish_num = 0;
	change = 0;
	thread = pms->thread;
	
	size = 0;
	ant = pms->first_ant;
	for(i = 0; i < thread; i++)
		size += ant[i].buf_size; /*if download stop, it should cause different buffer size for every fragment*/
	buffer_start = malloc(size);
	size = 0;
	data = malloc(PACKSIZE * thread);
	chdir(pms->file_path);
	for(i = 0; i < thread; i++) {
		ant->first = 1;
		ant->fd = open(pms->file, O_WRONLY|O_CREAT, 0644);
		ant->offset = 0;
		ant->packsize = PACKSIZE;
		ant->bytesleft = ant->end - ant->position + 1 ;
		lseek(ant->fd, ant->position, 0);
		ant->data = &data[PACKSIZE * i];
		ant->buffer_start = &buffer_start[size];
		size += ant->buf_size;
		ant->buffer = ant->buffer_start;
		ant++;
	}
	
	if(pms->port == 80)
		down_http_init(pms);
	else if(pms->port == 21)
		down_ftp_init(pms);
	
	while(finish_num < thread) {
		state = wait_read_all(pms, &rfdset, 30);
		if(state < 1) {
			sleep(2);
			continue;
		}
		ant = pms->first_ant;
		change = 0;
		j = 1;
		for(i = 0; i < thread; i++) {
			if(FD_ISSET(ant[i].s, &rfdset)) {
				ret = fill_buffer(&ant[i], &change);
				if(ret == -1) {
					goto bad_head;
				}
				if(ret == 2)
					finish_num++;
				if(state < j++)
					break; /*faster exit*/
			}
		}
		
		if((pms->flag & BREAKPOINT) && (change))
			state_save(pms);
		speed = statistic(pms->first_ant);
		if(speed < LOWSPEED)
			pms->flag |= NOBUFFER;
		else
			pms->flag &= ~NOBUFFER;
	}
	ret = 0;
bad_head:
	for(i = 0; i < thread; i++) {
		if(ant[i].s) {
			close(ant[i].s);
			ant[i].s = 0;
		}
		if(ant[i].fd) {
			close(ant[i].fd);
			ant[i].fd = 0;
		}
		if((pms->port == 21) && (ant[i].s_c)) {
			close(ant[i].s_c); /*close ftp control socker*/
			ant[i].s_c = 0;
		}
	}	
	free(pms->first_ant->buffer_start);
	pms->first_ant->buffer_start = NULL;
	free(pms->first_ant->data);
	pms->first_ant->data = NULL;
	return ret;
}	
	
void download(struct mission *pms) {
	int time;
	struct timeval now, last;
	char file[MAX_HOSTNAME];
	gettimeofday(&last, NULL);
	down_fragment(pms);
	gettimeofday(&now, NULL);
	time = (now.tv_sec - last.tv_sec) * 1000000 +(now.tv_usec - last.tv_usec);
	time /= 1000;
	printf("cost time %d.%d s\n", (time / 1000), (time / 10));
	printf("100%% download\n");
	if(pms->flag & BREAKPOINT) {
		close(pms->fd);
		pms->fd = 0;
		sprintf(file, "%s%s.nat", pms->file_path, pms->file);
		del_file(file);
	}
	free(pms->first_ant);
	pms->first_ant = NULL;
}	
	
int del_file(char *file) {
	unlink(file);
	return 1;
}	
